js-08 数组

一、数组常用方法

添加与删除 splice

  1. 从前添加或删除单个unshiftshift

    unshift();从前添加多个,返回数组的长度,改变数组

    shift();没有参数,删除第一项,返回删除值,

  2. 从后添加或删除单个pushpop

    push();从后添加多个,返回数组长度,改变数组;

    pop();不写参数,从后删除一项,返回被删除的值。

    arr.push(arr.shift()); 删除第一项并添加到最后一项;

  3. 复合写法splice(n[,m][,add])

    • 一个参数:删除n个;

    • 两个参数:从n删除m个;

    • 三个以上:从n删除m个,并开始添加;

    改变原数组,返回被删除的项组成的数组,如果没有被删除的项,返回一个空数组;

    注意:如果第一个为负数,表示从后面截取多少个,第二个参数则无效,第三个参数为添加;

    如果是负数,是添加的数组的length,因此是从后面截取;

排序 sort

变量.sort();默认是以字符串的Unicode码一位一位比较的;

  • function(a,b){return a-b} 从小到大或从大到小,字符串不按照一位一位比较,返回排序完的数组,原数组改变。

  • function(a,b){return a.localeCompare(b)} 字符串从小到大或从大到小。

  • function(a,b){return a.localeCompare(b,"zh")} 字符串包含汉字以26字母从小到大或从大到小排列。

    sort还可以对对象排序

  • 乱序:arr.sort(function(){return Math.random()-0.5})

    理解:正序是大于1,倒序是小于1,乱序则在中间;

当数组对象的时候,这样使用;

var ary3=[
    {name:"战三",age:20},
    {name:"李四",age:18},
    {name:"王五",age:28}
]
ary3.sort(function(a,b){
    return a.name.localeCompare(b.name,"zh")
})

拼接 join

arr.join()

把数组变成字符串,参数就是拼接符;

将数组逗号连接字符串,如果括号为引号则拼接;可以指定字符串拼接;

参数也可以是标签,返回设置好的变量,原数组不会改变。

小练习:查找文字

var h = p.innerHTML;
btn.onclick = function(){
    var val = input.value;  //获取value值
    var ary = h.split(val); //以搜索框的文字来分割成数组
    var str = ary.join('<mark>' + val + '</mark>'); //join将分隔好的数组组成字符串;
    p.innerHTML = str;  //把组好的字符串返回文本标签;
}

翻转 reverse

arr.reverse()

数组倒过来排序,不用传参数,返回值倒过来的数组,原数组变为倒过来的;

合并 concat

arr1.concat(arr2)

将多个数组拼接起来,参数要拼接的数组,多个用逗号隔开,

参数可以是数组,也可以是单个项,

返回值,拼接后的数组,原数组不改变;

ES6的语法:[...arr1,...arr2],不用使用concat()

查找 indexOf

arr.indexOf(str)

查找,参数要查找的值,返回值有则返回下标,没有返回-1,原数组不会改变;,

查找的值要全等,IE8及以下不支持;

截取 slice

arr.slice(n, m)

和字符串用法一样;

  • 不填参数,返回全部;

  • 只有n时,从n开始截取到结束;,返回被截取的字符;

  • n,m时,从n截取到m,不包括m;

  • m大于n时,不置换下标,返回空字符串,不报错;

  • 当有值为负数时,负数加字符串长度,返回参数,加完还是负数,从0开始;

也可以使用arr.length = 10,将20个对象的数组只留前面10个;

总结:

会改变原数组的:push,pop,unshift,shift,splice,sort,reverse,fill,copyWithin

其他的方法都不会改变原数组;

实现a克隆b能想到几种方法

  1. for循环,
  2. slice截取不传参数,
  3. concat利用空数组拼接a;

二、数组迭代方法

下面所有的项都可以用for循环办到,只是将for循环包装了一下;

参数:function(数据,下标,数组本身){比较表达式}

全部 every

比较数组,返回布尔值,全真为真,一假全假。

返回值:true/false

不会改变原数组;

vue中实例:常用于在全选上面;

this.checkedAll = this.list.every(items=>items.flag)  
//当全部选中时,则改变checkdAll复选框的状态;

一些 some

比较数组,返回布尔值,一真为真,全假为假。

返回值:true/false;不会改变原数组;

过滤 filter

过滤,满足条件的组成新数组并返回。

ary.filter(function(value){
    value>3   将大于3的数组成一个新数组,并返回;
});

改变 map

改变数组中的数据,返回改变的数组

定义:对数组的每一项都运行给定函数,返回每次函数调用的结果组成的数组;

比如:变量.map((value,index,ary){return value*3});让每一个value都*3并返回新数组;

map可以直接修改引用类型的数组(数组套对象),不需要返回;

// 第三个参数的使用方法
let a = [1,2,3,4,5,6]
let b = a.map((e,i,arr)=>{
    return `${e}${i}${arr.find(e=>e%5===1)}`
})

//map修改数组对象优雅的方式
arr.map(item=>{
    return {
        ...item,
        obj: JSON.parse(item.obj)  //使用对象解构的方式,之后再替换里面的对象;
    }
})

循环 forEach

循环数组,参数是一样的,没有返回值,不需要return,原数组不改变;和for循环使用方法一样,

循环元素时第一个形参是每一个元素;

参数funtion(数据,下标,数组本身);,IE8以下不兼容;

只能循环NodeList数组:不能循环HTMLCollection类数组,对于HTMLCollection的数组,可以使用Array.from进行转换

var ary = [1,2,4,5,6,7]
var str = '';
var f = ary.forEach(function(value){
    str+=value;
})
console.log(str);   //返回是1234567;

注意:

使用迭代数组无法终止数组循环,不能break和containue,解决办法如下:

  1. 使用try代码块,在需要中断的地方抛出异常

    let sum = 0
    try{
        Array.from({length:100}).map((_,i)=>i).forEach(item=>{
            sum+=1
            if(item === 50) throw Error()
        })
    } catch (e){}
    
  2. 使用every、some来代替forEach循环解决,every可以return false来终止循环,some可以return true来终止循环,强烈推荐使用some,简单方便;

    let sum = 0;
    let arr = Array.from({length:100}).map((_,i)=>i)
    // every写法
    arr.every(item=>{
      sum+=1
      if(item > 50){
        return false
      }
      return true
    })
    console.log(sum) // 52
    // some写法
    arr.some(item=>{
        sum+=1
        if(item > 50) return false  // some不需要在后面返回true了
    })
    

三、数组扩展

  1. Array.from(v) : 将伪数组对象或可遍历对象转换为真数组,需要一个变量来接收;

  2. Array.of(v1, v2, v3) : 将一系列值转换成数组,和new Array传值是一样的;

var arr = Array.of(3) // 这个3不是数组的长度,是一个数组
  1. find();find返回符合条件的元素,之后的值不会再调用执行函数,如果找不到返回undefined,不会改变原数组;
arr.find((item,index,arr)=>{})
  1. findIndex:返回符合条件的元素的索引位置,没有返回-1,不改变原数组;
findIndex(item,index,arr=>{})
  1. includes():判断数组是否包含否个值

  2. fill() 如果括号不填参,则将数组的每一项都返回undefined;

括号共有三个参数,第一个要置换的内容(必填),第二个从什么位置开始,第三个停止置换的前一个位置;

  1. flat() 将一个多维数组深度转成一维(扁平化或称作降维);

常用于扁平化数组,括号填写扁平化多少层,填写Infinity,扁平所有;

flat方法会移除数组中的空项,[1,2,,4,5],会将2和4中间的移除;

var arr = [1,2,[1,2,[1,2,3,[1,2,]]]]
arr.flat(1)   // 遍历一组
  1. flatMap() 相当于flat和map的组合,在运算后直接将数组扁平化处理

    let a = [1,2,[3],[4,5]]
    let b = a.flatMap(e=>e+1)
    let c = a.map(e=>e+1).flat()
    console.log(b)  // [2,3,'31','4,51']
    console.log(c)  // 如上
    
  2. copyWithin能复制数组中的某些元素,并将它放到同一个数组指定的位置;

三个参数:第一个要置换的位置(必填),第二个从什么位置开始复制,第三个要停止复制的前一个位置;

  1. valueOf() 返回原数组,可以用来浅拷贝;
  2. keys() 返回数组的下标;会生成iterator接口,使用for..of取值或者Array.from;

四、reduce

arr.reduce(callback,[init])

第一个回调函数,回调函数必须有返回值,,一般返回第一个prev的值;

回调函数的四个参数:上一次回调函数的结果,当前值,项的索引,数组对象;

第二个参数为可选值,用于初始值,放到callback里进行计算;(可以放一个空数组,之后可以使用第一个值concat)

reduceRight

从后边开始循环;

理解:

let arr = [1,2,3,4,5]
let arr2 = arr.reduce((p,c)=>{
    return p
},[])
// 第二个参数作为p第一次循环的值,如果没有则取数组中第一项,则c从第二项开始;
// p为上一次回调函数的结果,如果没有return p,则没有结果,则reduce没有意义; 
// 上述代码中,p没有和c进行交互,p一直是[],则arr2结果为[];

简单使用

var arr = [1,2,3,4,5]
var sum = arr.reduce((x,y)=>x+y)   //15
var mul = arr.reduce((x,y)=>x*y)  //

数组去重案例

let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
    }
},[])  // 当有第二个参数时,第一次先循环一个空数组,第二次循环添加了一个的数组
console.log(newArr);// [1, 2, 3, 4]

对象案例

仅演示使用方法,案例不在

arr.reduce((pre,curr)=>{
    if(curr.length === maxLen){
        pre[curr[0]] = curr.length;   // 将pre对象的键名,等于键值,这里的curr[0]为第一个;
    }
    return pre // 返回的是上一次回调函数的结果,最终会把最后一个结果返回出去;
},{})

修改对象的值(常用)

var obj = {
  name: 'hhh',
  age: '18'
}
var obj2 = Object.keys(obj).reduce((o, c) => {
  o[c] = 18 + obj[c]
  return o
}, {})

console.log(obj2) // {name: '18hhh', age: '1818'}

reduce代替map

let arr = [0,1,2,3,4,5]
arr.map(item=>item*2)
arr.reduce((p,c)=>{
    p.push(c*2)
    return p
},[])

reduce代替filter

let arr = [0,1,2,3,4]
arr.filter(item=>item<3)
arr.reduce((p,c)=>{
    c < 3 && p.push(c)
    return p
},[])

补充

将类数组转换为真正的数组

该函数可以调用任意数量的形参;

  • Array.prototype.slice.call(arguments);

  • [].slice.call(arguments)

  • Array.from(arguments)

function addOne(){
    return Array.prototype.slice.call(arguments)
}
console.log(addOne(1,2,3,4,5))

添加指定长度的空数组

Array.apply(null,{length:20})

会加入20个空的数组,20个长度的数组;

可以简写:Array.apply(null,Array(20))

es6写法:Array.from({length:20})或Array(20).fill(); // fill里面也可以填写内容,以什么来填充;

Array.apply(null,{length:20}).map((item,index)=>{
     console.log(index)  // 打印20次
})

修改原数组代替concat

Array.prototype.push.apply(arr1,arr2)

由于concat不能直接修改原数组,可以通过此方法将arr2合并到arr1身上,apply第二个参数接受数组;

缺点:如果第二个数组太大时,不建议使用此方法来进行合并,因为一个函数能够接收的参数个数是有限的;

解决方法:将参数数组切块后循环传入目标

function concatOfArray (arr1, arr2) {
  var QUANTUM = 32768;
  for (var i = 0, len = arr2.length; i < len; i += QUANTUM) {
    Array.prototype.push.apply(
      arr1,
      arr2.slice(i, Math.min(i + QUANTUM, len))
    );
  }
  return arr1;
}
// 验证: 
var arr1 = [1,2,3,4]
var arr2 = []
for(var i=0;i<1000000;i++){arr2.push(i)}   //循环添加多个i
Array.prototype.push.apply(arr1,arr2)   //爆栈
concatOfArray(arr1,arr2) //正常输出;

测试随机数取哪些数

如果需要测试随机数都取到了哪些数,通过创建空数组方法,添加到数组里面去;

Array.from({length:20}).map(i=>Math.ceil(Math.random()*6)))

随机验证码

Math.random().toString(36).substr(2,6)

高频面试题

数组去重

判断问题:拿第一个和每一个去做比较,所以需要用到两个循环;

  1. 写两个循环,第一个循环数组第一个数据,第二个循环让每一个和第一个做比较。
  2. 第二循环j=i+1,再判断第二个循环的下标是否和第一个循环的下标的字符相同,如果是相同的,则删除一个
  3. 删除之后数组变短,让j减一,重新做比较。
for(var i=0;i<ary.length;i++){
    for(var k=i+1;k<ary.length;k++){ 
        if(ary[i] === ary[k]){
            if(i == k){  //如果k等于0,则需要这一步
                continue;
            }
            ary.splice(k,1);
            k--; //前面删除一个之后数组的长度会进1,此时的k会直接跳到循环去加1,让k减1;
        }
    }
}

有序数组去重

function removeDuplicates(nums) {
    for(let i=0;i<nums.length;i++) {
        nums.splice(i, nums.lastIndexOf(nums[i]) - i);
    }
}

// or
var removeDuplicates = function (nums) {
  if(!nums.length || !nums) return 0;
  let left = 0;
  for(let right=1;right<nums.length;right++) {
    if(nums[left] !== nums[right]) {
      nums[++left] = nums[right]
    }
  }
  return ++left;
};

选择数组排序

判断问题:拿第一个和每一个去做比较,如果大于第二个,则让第一个到第二个位置去;

(1)写两个循环,第二个循环j=i+1;,不比较本身。

(2)判断数组的第一个字符i是否大于数组的第二个j字符,如果大于第一个字符,则调换位置。

冒泡数组排序

跟自己紧挨着的做比较,j>j+1,判断大于调换位置。

for(var i=0;i<ary.length;i++){
    for(var k=0;k<ary.length;k++){
        if(ary[k] > ary[k+1]){ //2k > 1n 1n 2k
            var n=ary[k];
            ary[k]=ary[k+1];
            ary[k+1]=n;
        } //自身和紧挨着的后一个比较;
    }
}

其他

● 数组方法 pop() push() unshift() shift() ?

● split() join() 的区别?

● 编写一个数组去重的方法。

● [“1”, “2”, “3”].map(parseInt) 答案是多少?

● 冒泡算法排序?